package no.rodland.acc.parsers;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import no.rodland.acc.converters.DateConverter;
import no.rodland.acc.model.BankAccount;
import no.rodland.acc.model.Budget;
import no.rodland.acc.model.BudgetAccount;
import no.rodland.acc.model.TextBudgetAccount;
import no.rodland.acc.db.FetchService;
import no.rodland.acc.utils.Config;
import no.rodland.acc.utils.NumberUtil;

/**
 * Created by IntelliJ IDEA.
 *
 * @Since: Jun 24, 2010 11:06:00 PM
 * @Author: fmr
 */
public class BudgetParser extends AbstractParser {
    private static final Logger log = Logger.getLogger(BudgetParser.class);
    private FetchService fetchService;

    private boolean isOldType;
    private static final int MAX_COL_INDEX = 18;
    private static final Pattern COMMAS = Pattern.compile("^,+$");

    private static final String IMPORTED_FROM_BUDGET = "Imported from budget";

    private static final int DEFAULT_BANK_ACCOUNT_ID = 10;
    private static final int HYBEL_BANK_ACCOUNT_ID = 12;
    private static final int SPAR_BANK_ACCOUNT_ID = 11;

    private static final int POCKET_BUDGET_ACCOUNT_ID = 1;
    private static final int OVERFORSEL_BUDGET_ACCOUNT_ID = 98;
    private static final int MISC_BUDGET_ACCOUNT_ID = 98;
    private static final int HYBEL_INCOME_BUDGET_ACCOUNT_ID = 105;
    private static final int MISC_INCOME_BUDGET_ACCOUNT_ID = 109;
    private static final int INCOME_BUDGET_ACCOUNT_ID = 100;
    private static final int BONUS_BUDGET_ACCOUNT_ID = 101;

    public BudgetParser(FetchService fetchService,
                        TextDateParser textDateParser,
                        DateParser dateParser,
                        NumberParser numberParser,
                        boolean isOldType) {
        super(textDateParser, dateParser, numberParser);
        this.isOldType = isOldType;
        this.fetchService = fetchService;
    }

    @Override
    public void configure() {
        this.separator = Config.getProperty("import.budget.separator");
    }

    @SuppressWarnings({"unchecked", "ConstantConditions"})
    @Override
    public List<Budget> parseLine(String line) throws ParseException {
        try {
            List<Budget> list = new ArrayList<Budget>();
            String[] arr = line.split(separator);
            log.trace("line = " + line);
            int length = arr.length;

            DateTime date = dateParser.parse(arr[0]);
            log.trace("date = " + new DateConverter().convertToString(null, date));
            String text = arr[1];

            int i = 2;
            int index = text.indexOf("\"");
            if (index > -1) {
                while (text.indexOf("\"", index + 1) == -1) {
                    text = text + "," + arr[i++];
                }
            }
            text = text.replaceAll("[\"']", "").trim();

            String remember = "N";
            if (text.startsWith("HUSK")) {
                remember = "Y";
            }

            addAll(list, handleInnSpar(date, text, arr, i++, remember));
            addAll(list, handleInnHybel(date, text, arr, i++, remember));
            if (!isOldType) {
                addAll(list, handleUtHybel(date, text, arr, i++, remember));
                addAll(list, handleInnXtra(date, text, arr, i++, remember));
            }
            addAll(list, handleInnMF(date, text, arr, i++, remember));

            for (int j = 1; j <= MAX_COL_INDEX; j++) {
                BudgetAccount ba = getBudgetAccount(j);
                BankAccount bankAcc = fetchService.getBankAccounts().get(DEFAULT_BANK_ACCOUNT_ID);
                if (ba != null) {
                    addAll(list, handleRegular(date, text, ba, bankAcc, arr, i++, remember));
                }
            }

            addAll(list, handleUtSpar(date, text, arr, i++, remember));

            addAll(list, handleUtHybel(date, text, arr, i, remember));

            if (list.size() == 0) {
                Budget b = createBudget(date,
                        text,
                        fetchService.getBudgetAccounts().get(MISC_BUDGET_ACCOUNT_ID),
                        fetchService.getBankAccounts().get(DEFAULT_BANK_ACCOUNT_ID),
                        remember,
                        0);
                addAll(list, makeList(b));
            }
            return list;
        }
        catch (ParseException e) {
            throw e;
        }
        catch (Exception e) {
            log.error("Unable to parse line: " + line, e);
            throw new ParseException("Unable to parse line", line, e);
        }
    }

    private void addAll(List<Budget> list, List<Budget> candidates) {
        if (candidates != null && candidates.size() > 0) {
            list.addAll(candidates);
        }
    }

    private List<Budget> handleInnSpar(DateTime date, String text, String[] arr, int i, String remember) {
        if (hasData(arr, i)) {
            log.trace("handling spar: " + arr[i]);
            BankAccount bankAcc = fetchService.getBankAccounts().get(SPAR_BANK_ACCOUNT_ID);
            BudgetAccount budAcc = fetchService.getBudgetAccounts().get(MISC_INCOME_BUDGET_ACCOUNT_ID);
            int amount = -1 * NumberUtil.getInt(arr[i]);
            Budget b = createBudget(date, text, budAcc, bankAcc, remember, amount);
            return makeList(b);
        }
        return null;
    }

    private List<Budget> handleInnHybel(DateTime date, String text, String[] arr, int i, String remember) {
        if (hasData(arr, i)) {
            log.trace("handling inn hybel: " + arr[i]);
            BankAccount bankAcc = fetchService.getBankAccounts().get(HYBEL_BANK_ACCOUNT_ID);
            BudgetAccount budAcc = fetchService.getBudgetAccounts().get(HYBEL_INCOME_BUDGET_ACCOUNT_ID);
            int amount = -1 * NumberUtil.getInt(arr[i]);
            Budget b = createBudget(date, text, budAcc, bankAcc, remember, amount);
            return makeList(b);
        }
        return null;
    }

    private List<Budget> handleUtSpar(DateTime date, String text, String[] arr, int i, String remember) {
        if (hasData(arr, i)) {
            log.trace("handling spar to bruk: " + arr[i]);
            return handleTransfer(date, text, arr, i, fetchService.getBankAccounts().get(SPAR_BANK_ACCOUNT_ID), remember);
        }
        return null;
    }

    private List<Budget> handleUtHybel(DateTime date, String text, String[] arr, int i, String remember) {
        if (hasData(arr, i)) {
            log.trace("handling hybel to bruk (II): " + arr[i]);
            return handleTransfer(date,
                    text,
                    arr,
                    i,
                    fetchService.getBankAccounts().get(HYBEL_BANK_ACCOUNT_ID),
                    remember);
        }
        return null;
    }

    private List<Budget> handleTransfer(DateTime date,
                                        String text,
                                        String[] arr,
                                        int i,
                                        BankAccount fromBankAccount,
                                        String remember) {
        if (hasData(arr, i)) {
            BankAccount regularAccount = fetchService.getBankAccounts().get(DEFAULT_BANK_ACCOUNT_ID);
            BudgetAccount budAcc = fetchService.getBudgetAccounts().get(OVERFORSEL_BUDGET_ACCOUNT_ID);
            int amount = NumberUtil.getInt(arr[i]);
            Budget bout = createBudget(date, text, budAcc, fromBankAccount, remember, amount);
            Budget bin = createBudget(date, text, budAcc, regularAccount, "N", (-1 * amount));
            return makeList(bout, bin);
        }
        return null;
    }

    private List<Budget> handleInnXtra(DateTime date, String text, String[] arr, int i, String remember) {
        if (hasData(arr, i)) {
            log.trace("handling inn xtra: " + arr[i]);
            BankAccount bankAcc = fetchService.getBankAccounts().get(DEFAULT_BANK_ACCOUNT_ID);
            BudgetAccount budAcc = fetchService.getBudgetAccounts().get(MISC_INCOME_BUDGET_ACCOUNT_ID);
            int amount = -1 * NumberUtil.getInt(arr[i]);
            Budget b = createBudget(date, text, budAcc, bankAcc, remember, amount);
            return makeList(b);
        }
        return null;
    }

    private List<Budget> handleInnMF(DateTime date, String text, String[] arr, int i, String remember) {
        if (hasData(arr, i)) {
            log.trace("handling inn mf: " + arr[i]);
            BankAccount bankAcc = fetchService.getBankAccounts().get(DEFAULT_BANK_ACCOUNT_ID);
            BudgetAccount budAcc;
            if (text.toLowerCase().startsWith("lønn")) {
                budAcc = fetchService.getBudgetAccounts().get(INCOME_BUDGET_ACCOUNT_ID);
            }
            else if (text.toLowerCase().startsWith("husk: lønn")) {
                budAcc = fetchService.getBudgetAccounts().get(INCOME_BUDGET_ACCOUNT_ID);
            }
            else if (text.toLowerCase().startsWith("bonus")) {
                budAcc = fetchService.getBudgetAccounts().get(BONUS_BUDGET_ACCOUNT_ID);
            }
            else if (text.toLowerCase().startsWith("lommepenger")) {
                budAcc = fetchService.getBudgetAccounts().get(POCKET_BUDGET_ACCOUNT_ID);
            }
            else {
                budAcc = fetchService.getBudgetAccounts().get(MISC_INCOME_BUDGET_ACCOUNT_ID);
            }
            if (bankAcc != null && budAcc != null) {
                int amount = -1 * NumberUtil.getInt(arr[i]);
                Budget b = createBudget(date, text, budAcc, bankAcc, remember, amount);
                return makeList(b);
            }
        }
        return null;
    }

    private List<Budget> handleRegular(DateTime date,
                                       String text,
                                       BudgetAccount budgetAccount,
                                       BankAccount bankAccount,
                                       String[] arr,
                                       int i, String remember) {
        if (hasData(arr, i) && budgetAccount != null) {
            log.trace("handling reg " + i + ": " + arr[i] + " with ba: " + budgetAccount);
            int amount = NumberUtil.getInt(arr[i]);
            Budget b = createBudget(date, text, budgetAccount, bankAccount, remember, amount);
            return makeList(b);
        }
        return null;
    }

    private Budget createBudget(DateTime date,
                                String text,
                                BudgetAccount budgetAccount,
                                BankAccount bankAccount,
                                String remember, int amount) {
        BudgetAccount budgetAccountFromText = TextBudgetAccount.getBudgetAccountForText(text,
                fetchService.getTextBudgetAccounts());

        if (budgetAccountFromText != null) {
            log.debug("Switching ba from " + budgetAccount + " to " + budgetAccountFromText);
            budgetAccount = budgetAccountFromText;
        }
        return new Budget(date,
                budgetAccount,
                text,
                amount,
                bankAccount,
                "Y",
                "N",
                remember,
                IMPORTED_FROM_BUDGET);
    }

    private boolean hasData(String[] arr, int index) {
        return arr != null && arr.length > index && arr[index] != null && arr[index].length() > 0;
    }

    @Override
    public String getName() {
        return "Budsjett " + (isOldType ? "03-04" : "05-");
    }

    @Override
    public boolean isParseble(String line) {
        if (line == null){
            return false;
        }
        if (COMMAS.matcher(line).matches()) {
            return false;
        }

        log.trace("line = " + line);
        boolean parse = super.isParseble(line) && line.split(separator).length > 0 && dateParser.parse(line.split(
                separator)[0]) != null;
        if (!parse) {
            log.debug("unparsable: " + line);
        }
        return parse;
    }

    public BudgetAccount getBudgetAccount(int i) throws SQLException, ParseException {
        Map<Integer, BudgetAccount> budgetAccounts = fetchService.getBudgetAccounts();
        if (isOldType) {
            if (i == 5 || i == 6 || i == 8 || i == 15 || i == 16 || i == 17) {
                return null;
            }
        }

        if (i == 1) {
            return budgetAccounts.get(10);
        }
        else if (i == 2) {
            return budgetAccounts.get(11);
        }
        else if (i == 3) {
            return budgetAccounts.get(36);
        }
        else if (i == 4) {
            return budgetAccounts.get(20);
        }
        else if (i == 5) {
            return budgetAccounts.get(21);
        }
        else if (i == 6) {
            return budgetAccounts.get(22);
        }
        else if (i == 7) {
            return budgetAccounts.get(30);
        }
        else if (i == 8) {
            return budgetAccounts.get(31);
        }
        else if (i == 9) {
            return budgetAccounts.get(46);
        }
        else if (i == 10) {
            return budgetAccounts.get(52);
        }
        else if (i == 11) {
            return budgetAccounts.get(39);
        }
        else if (i == 12) {
            return budgetAccounts.get(80);
        }
        else if (i == 13) {
            return budgetAccounts.get(81);
        }
        else if (i == 14) {
            return budgetAccounts.get(60);
        }
        else if (i == 15) {
            return budgetAccounts.get(40);
        }
        else if (i == 16) {
            return budgetAccounts.get(79);
        }
        else if (i == 17) {
            return budgetAccounts.get(90);
        }
        else if (i == 18) {
            return budgetAccounts.get(99);
        }
        throw new ParseException("No BudgetAccount found for index: " + i, null);
    }
}
